﻿// StudyKit - .Net Library v1.1
//
// 2009.09.07 - v1.1 Release

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace StudyKitLib
{
    public class RF1356
    {
        private static System.IO.Ports.SerialPort[] serialPort = new System.IO.Ports.SerialPort[50];

        /******************************************
         * Function    : BaudDetec(포트번호)
         * Description : RFID Reader Baudrate 확인 함수
         *               Reader Baudrate 와 COM Port Baudrate가 동일할 경우 Response 들어옴
         *               Response 없으면 Baudrate 설정 잘못되었음을 의미
         * Return      : REPLY
         *****************************************/
        public static REPLY BaudDetec(long PortNO)
        {
            try
            {
                Byte[] cmd = new byte[6];
                int idx = 0;
                string msg1 = String.Empty;

                cmd[idx++] = 0x06;
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0x52;
                cmd[idx++] = 0x00;
                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                serialPort[PortNO].Write(cmd, 0, idx);
                System.Threading.Thread.Sleep(50);
                Byte[] Buffer = ReceiveData(PortNO);

                if (Buffer[0] == 0x06)
                {
                    return REPLY.RF_OK;
                }
                else
                    return REPLY.RF_ERR;

            }
            catch (Exception ex)
            {
                return REPLY.RF_ERR;
            }
        }

        /******************************************
         * Function    : RFReaderOpen(포트번호)
         * Description : 시리얼 포트 열기
         *               COM Port Setting
         *                - Baudrate : 38400
         *                - Data Bit : 8
         *                - Parity : Even(짝수)
         *                - Stop Bit : 1
         *                - Flow Control : None
         * Return      : bool
         *****************************************/
        public static bool RFReaderOpen(long PortNO)
        {
            try
            {
                //통신용 객체 생성
                serialPort[PortNO] = new System.IO.Ports.SerialPort();

                serialPort[PortNO].PortName = string.Format("COM{0}", PortNO);
                serialPort[PortNO].BaudRate = 38400;//초당 처리 가능한 신호 갯수
                serialPort[PortNO].Parity = System.IO.Ports.Parity.Even;//데이터 변조 확인용 bit 처리 방식 설정
                serialPort[PortNO].DataBits = 8;//데이터 크기
                serialPort[PortNO].StopBits = System.IO.Ports.StopBits.One;//데이터 끝을 표시하는 데 사용할 bit option
                serialPort[PortNO].ReadTimeout = 3000;
                serialPort[PortNO].WriteTimeout = 3000;
                serialPort[PortNO].ReceivedBytesThreshold = 1;//버퍼에 저장할 데이터 크기

                //연결 열기
                serialPort[PortNO].Open();

                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /******************************************
         * Function    : RFReaderClose(포트번호)
         * Description : 시리얼 포트 닫기
         * Return      : bool
         *****************************************/
        public static bool RFReaderClose(long PortNO)
        {
            try
            {
                serialPort[PortNO].Close();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /******************************************
         * Function    : RFReaderIsOpen(포트번호)
         * Description : 시리얼 포트 사용중 여부 확인
         * Return      : bool
         *****************************************/
        public static bool RFReaderIsOpen(long PortNO)
        {
            try
            {
                if (serialPort[PortNO].IsOpen)
                {
                    return true;
                }
                else
                    return false;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /******************************************
         * Function    : RFReadSerialNO(포트번호, 태그시리얼번호(UID))
         * Description : 태그 시리얼 번호 읽기 함수
         * Return      : REPLY
         *****************************************/
        public static REPLY RFReadSerialNO(long PortNO, ref string pSerialNumbers)
        {
            try
            {
                Byte[] cmd = new byte[7];
                int idx = 0;
                int num = 0;
                string msg1 = String.Empty;

                cmd[idx++] = 0x07;
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0xB0;
                cmd[idx++] = 0x01;
                cmd[idx++] = 0x00;
                //데이터 변조 방지 설정
                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                serialPort[PortNO].Write(cmd, 0, idx);//정보 요청

                System.Threading.Thread.Sleep(50);

                Byte[] Buffer = ReceiveData(PortNO);//정보 수신

                if (Buffer[0] != 0x06)
                {
                    for (int i = 7; i < Buffer.Length; i++)
                    {
                        msg1 += String.Format("{0:X2}", Buffer[i]);
                    }

                    num = Buffer[0];
                    num = (num - 7) / 10;

                    for (int i = 0; i < num; i++)
                    {
                        pSerialNumbers = pSerialNumbers + msg1.Substring(20 * i, 16);
                    }

                    pSerialNumbers = string.Format("{0:00}", num) + pSerialNumbers;

                    return REPLY.RF_OK;
                }
                else
                    return REPLY.NO_TRANSPONDER;

            }
            catch (Exception ex)
            {
                return REPLY.RF_ERR;
            }
        }

        /******************************************
         * Function    : RFReadDataBlock(포트번호, 태그시리얼번호(UID), 데이터블럭, 시작블럭, 블럭크기)
         * Description : 태그 데이터 블럭 읽기
         *               읽고자 하는 태그의 UID를 파라미터로 입력하여 데이터를 읽어옴
         *                - PortNO         : 포트 번호
         *                - pSerialNumbers : 태그시리얼번호(UID)
         *                - pDataBlocks    : 읽은 데이터
         *                - StartBlock     : 읽을 데이터블럭 시작주소(0 ~ 27)
         *                - BlockLength    : 읽을 데이터블럭 크기(1 ~ 28)
         *                - 한 블럭당 크기 : 4 Byte
         * Return      : REPLY
         *****************************************/
        public static REPLY RFReadDataBlock(long PortNO, string pSerialNumbers, ref string pDataBlocks, int StartBlock, int BlockLength)
        {
            string msg1 = String.Empty;
            string msg2 = String.Empty;
            int num = 0;

            if (pSerialNumbers.Length != 16)
                return REPLY.INVALID_PARAMETER;

            Byte[] bUID = new byte[8];

            try
            {
                bUID = GetByteArray(pSerialNumbers);
            }
            catch (Exception ex)
            {
                return REPLY.INVALID_PARAMETER;
            }

            if (StartBlock < 0 || StartBlock > 27)
                return REPLY.INVALID_PARAMETER;

            if (BlockLength < 1 || BlockLength > 28)
                return REPLY.INVALID_PARAMETER;

            if ((StartBlock + BlockLength < 1) || (StartBlock + BlockLength > 28))
                return REPLY.INVALID_PARAMETER;

            try
            {
                Byte[] cmd = new byte[17];
                // 11 FF B0 23 01 00 00 00 00 01 C8 C7 A9 00 08 99 B6

                int idx = 0;
                cmd[idx++] = 0x11;
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0xB0;
                cmd[idx++] = 0x23;
                cmd[idx++] = 0x01;

                int index = 7;
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];

                byte start = new byte();
                byte length = new byte();
                start = (byte)StartBlock;
                length = (byte)BlockLength;
                cmd[idx++] = start;
                cmd[idx++] = length;

                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                //데이터 요청
                serialPort[PortNO].Write(cmd, 0, idx);

                System.Threading.Thread.Sleep(50);

                Byte[] Buffer = ReceiveData(PortNO);//데이터 수신


                string msg = String.Empty;

                if (Buffer[0] != 0x06)
                {
                    for (int i = 6; i < Buffer.Length - 2; i++)
                    {
                        msg1 = msg1 + String.Format("{0:X2}", Buffer[i]);
                    }

                    num = Buffer[4];

                    for (int i = 0; i < num; i++)
                    {
                        msg2 = msg1.Substring(10 * i, 10);

                        pDataBlocks += msg2.Substring(2, 8);
                    }

                    return REPLY.RF_OK;
                }
                else
                    return REPLY.NO_TRANSPONDER;
            }
            catch (Exception ex)
            {
                return REPLY.RF_ERR;
            }
        }

        /******************************************
         * Function    : RFWriteDataBlock(포트번호, 태그시리얼번호(UID), 데이터블럭, 시작블럭, 블럭크기)
         * Description : 태그 데이터 블럭 쓰기
         *               데이터를 저장하고자 하는 태그의 UID와 데이터값을 파라미터로 입력하여 데이터를 저장함
         *                - PortNO         : 포트 번호
         *                - pSerialNumbers : 태그시리얼번호(UID)
         *                - pDataBlocks    : 저장할 데이터
         *                - StartBlock     : 저장할 데이터블럭 시작주소(0 ~ 27)
         *                - BlockLength    : 저장할 데이터블럭 크기(1 ~ 28)
         *                - 한 블럭당 크기 : 4 Byte
         * Return      : REPLY
         *****************************************/
        public static REPLY RFWriteDataBlock(long PortNO, string pSerialNumbers, ref string pDataBlocks, int StartBlock, int BlockLength)
        {
            if (pSerialNumbers.Length != 16)
                return REPLY.INVALID_PARAMETER;

            Byte[] bUID = new byte[8];
            Byte[] bWriteData;

            for (int i = 0; i < 112; i++)
            {
                pDataBlocks = pDataBlocks + "0";
            }            

            try
            {
                bUID = GetByteArray(pSerialNumbers);

                bWriteData = GetByteArray(pDataBlocks.Substring(0, BlockLength * 8));
            }
            catch (Exception ex)
            {
                return REPLY.INVALID_PARAMETER;
            }

            if (StartBlock < 0 || StartBlock > 27)
                return REPLY.INVALID_PARAMETER;

            if (BlockLength < 1 || BlockLength > 28)
                return REPLY.INVALID_PARAMETER;

            if ((StartBlock + BlockLength < 1) || (StartBlock + BlockLength > 28))
                return REPLY.INVALID_PARAMETER;

            try
            {
                int data_len = 18 + bWriteData.Length;
                Byte[] cmd = new byte[data_len];
                int idx = 0;
                cmd[idx++] = Convert.ToByte(data_len); // LENGTH
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0xB0;
                cmd[idx++] = 0x24;
                cmd[idx++] = 0x01;

                int index = 7;
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];
                cmd[idx++] = bUID[index--];

                byte start = new byte();
                byte length = new byte();
                start = (byte)StartBlock;
                length = (byte)BlockLength;
                cmd[idx++] = start;
                cmd[idx++] = length;
                cmd[idx++] = 0x04;

                // Write Data.
                foreach (Byte b in bWriteData)
                {
                    cmd[idx++] = b;
                }

                // CRC
                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                //데이터 요청 (쓰기 요청)
                serialPort[PortNO].Write(cmd, 0, idx);

                System.Threading.Thread.Sleep(50);

                Byte[] Buffer = ReceiveData(PortNO);//결과 수신

                if (Buffer[3] == 0x00)
                {
                    return REPLY.RF_OK;
                }
                else if (Buffer[3] == 0x01)
                {
                    return REPLY.NO_TRANSPONDER;
                }
                else
                    return REPLY.RF_ERR;
            }
            catch (Exception ex)
            {
                return REPLY.RF_ERR;
            }
        }

        /******************************************
         * Function    : BeepOn(포트번호)
         * Description : RFID Reader의 Buzzer ON(준비중)
         * Return      : none
         *****************************************/
        public static void BeepOn(long PortNO)
        {
            try
            {
                Byte[] cmd = new byte[6];
                int idx = 0;
                cmd[idx++] = 0x06;
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0xA0;
                cmd[idx++] = 0x01;      // Beep On                
                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                serialPort[PortNO].Write(cmd, 0, idx);
                System.Threading.Thread.Sleep(50);
                Byte[] Buffer = ReceiveData(PortNO);
            }
            catch (Exception ex) { }
        }

        /******************************************
         * Function    : BeepOff(포트번호)
         * Description : RFID Reader의 Buzzer OFF(준비중)
         * Return      : none
         *****************************************/
        public static void BeepOff(long PortNO)
        {
            try
            {
                Byte[] cmd = new byte[6];
                int idx = 0;
                cmd[idx++] = 0x06;
                cmd[idx++] = 0xFF;
                cmd[idx++] = 0xA0;
                cmd[idx++] = 0x00;      // Beep Off  
                uint crc = RFCRC16Calc(cmd, idx);
                cmd[idx++] = (Byte)(crc & 0x00FF);
                cmd[idx++] = (Byte)((crc & 0xFF00) >> 8);

                serialPort[PortNO].Write(cmd, 0, idx);
                System.Threading.Thread.Sleep(50);
                Byte[] Buffer = ReceiveData(PortNO);
            }
            catch (Exception ex) { }
        }

        /******************************************
         * Variable    : REPLY
         * Description : 함수의 상태값
         *                - RF_OK             : Success
         *                - NO_TRANSPONDER    : 태그 없음
         *                - RF_ERR            : Fail
         *                - INVALID_PARAMETER : 잘못된 파라미터         
         *****************************************/
        public enum REPLY
        {
            RF_OK = 0x00,
            NO_TRANSPONDER = 0x01,
            RF_ERR = 0x02,
            INVALID_PARAMETER = 0x03
        }

        /******************************************
         * Function    : RFCRC16Calc(데이터, 크기)
         * Description : CRC 체크
         * Return      : crc
         *****************************************/
        private static uint RFCRC16Calc(Byte[] pDataStream, int pSize)
        {
            uint crcValue = 0xFFFF;
            int i, j;
            for (i = 0; i < pSize; i++)
            {
                crcValue ^= (uint)(pDataStream[i] & 0xFF);

                for (j = 0; j < 8; j++)
                {
                    uint ui = crcValue & Convert.ToUInt32(0x0001);
                    if (ui == 1)
                        crcValue = ((crcValue >> 1) ^ 0x8408) & 0xFFFF;
                    else
                        crcValue = (crcValue >> 1);
                }
            }
            return crcValue;
        }

        /******************************************
         * Function    : ReceiveData(포트번호)
         * Description : 시리얼 포트로 들어온 데이터 수신
         * Return      : Byte[]
         *****************************************/
        private static Byte[] ReceiveData(long PortNO)
        {
            string msg = String.Empty;

            // 첫번째 바이트가 데이터 길이
            int iBytesToRead = serialPort[PortNO].ReadByte();

            // 데이터 받을 버퍼 생성
            Byte[] readBuffer = new byte[Convert.ToInt32(iBytesToRead)];

            readBuffer[0] = (Byte)iBytesToRead;

            for (int i = 1; i < readBuffer.Length; i++)
            {
                readBuffer[i] = (Byte)serialPort[PortNO].ReadByte();
            }

            return readBuffer;
        }

        /******************************************
        * Function    : GetByteArray(String)
        * Description : String 값을 Byte 로 변환
        * Return      : Byte[]
        *****************************************/
        private static Byte[] GetByteArray(string str)
        {
            int cnt = 0;
            Byte[] rst = new byte[str.Length / 2];
            for (int i = 0; i < str.Length / 2; i++)
            {
                rst[i] = Byte.Parse(str.Substring(cnt, 2), System.Globalization.NumberStyles.HexNumber
                            | System.Globalization.NumberStyles.HexNumber);
                cnt += 2;
            }
            return rst;
        }

        public static string str2hex(string ASCII_Data)  // ASCII 값을 HEX 값으로 변환하는 함수
        {
            string HEX_Data = "";
            int Len_Count = 0, Convet_value = 0;

            for (Len_Count = 0; Len_Count < ASCII_Data.Length; Len_Count++)
            {
                Convet_value = Convert.ToInt32(ASCII_Data[Len_Count]);
                HEX_Data += string.Format("{0:X2}", Convet_value);
            }

            return HEX_Data;
        }

        public static string hex2str(string HEX_Data)    // HEX 값을 ASCII 값으로 표시하는 함수
        {
            string result = "";

            foreach (Match m in (new Regex(@"[0-9A-F]{2,2}", RegexOptions.IgnoreCase)).Matches(HEX_Data))
                result += ((char)Convert.ToInt32(m.Value, 16)).ToString();

            return result;
        }
    }
}
